package security.aes;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;

import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;


public class AES256 {

	//source : http://stackoverflow.com/questions/15554296/simple-java-aes-encrypt-decrypt-example
	//debug post 1
	//passage a 256 au lieu de 128 : http://stackoverflow.com/questions/6561771/java-aes-256-secure-key-generator-illegal-key-size

	public static void createAESKeyStore(String path, char[] storepass, char[] keypass, String AESAliasName){
		String ksName = path;
		String aliasKey = AESAliasName;
		final char[] storePass = storepass;
		final char[] keyPass = keypass;

		KeyGenerator kgen;
		try {
			kgen = KeyGenerator.getInstance("AES");
			kgen.init(256);
			SecretKey skey = kgen.generateKey();

			KeyStore ks = KeyStore.getInstance("JCEKS", "SunJCE");

			ks.load(null, storePass);
			ks.setKeyEntry(aliasKey, skey, keyPass, null);

			FileOutputStream fos = new FileOutputStream(ksName);
			ks.store(fos, storePass);
			fos.close();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (KeyStoreException e) {
			e.printStackTrace();
		} catch (NoSuchProviderException e) {
			e.printStackTrace();
		} catch (CertificateException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	public static SecretKey loadKeyFromKeyStore(String path, char[] storepass, char[] keypass, String AESAliasName){
		final char[] storePass = storepass;
		final char[] keyPass = keypass;
		KeyStore ks;
		SecretKey skey = null;

		try {
			ks = KeyStore.getInstance("JCEKS", "SunJCE");
			FileInputStream kis = new FileInputStream(path);
			ks.load(kis, storePass);
			skey = (SecretKey) ks.getKey("aesKey", keyPass);
			kis.close();
		} catch (UnrecoverableKeyException e) {
			e.printStackTrace();
		} catch (KeyStoreException e) {
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (NoSuchProviderException e) {
			e.printStackTrace();
		} catch (CertificateException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		return skey;
	}

	public static SecretKey generateRandomKey(){
		KeyGenerator kgen;
		SecretKey aesKey = null;
		try {
			kgen = KeyGenerator.getInstance("AES");
			kgen.init(256);
			aesKey = kgen.generateKey();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		return aesKey;
	}

	public static Cipher initCipherEncrypt(SecretKey key, IvParameterSpec ivspec){
		Cipher encryptCipher;
		try {
			encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
			encryptCipher.init(Cipher.ENCRYPT_MODE, key, ivspec);

			return encryptCipher;
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		} catch (InvalidAlgorithmParameterException e) {
			e.printStackTrace();
		}
		return null;
	}

	public static Cipher initCipherDecrypt(SecretKey key, IvParameterSpec ivspec){
		Cipher decryptCipher;
		try {
			decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
			decryptCipher.init(Cipher.DECRYPT_MODE, key, ivspec);

			return decryptCipher;
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		} catch (InvalidAlgorithmParameterException e) {
			e.printStackTrace();
		}
		return null;
	}

	public static byte[] encrypt(byte[] data, SecretKey aesKey, Cipher encryptCipher) {
		try {

			// Encrypt
			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
			CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, encryptCipher);

			cipherOutputStream.write(data);
			cipherOutputStream.flush();
			cipherOutputStream.close();
			byte[] encryptedBytes = outputStream.toByteArray();
			outputStream.close(); //rajouté, debug

			return encryptedBytes;

		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return null;
	}

	private static byte[] encryptUpdate(byte[] data, Cipher encryptCipher) {
		try {
			// Encrypt Update
			return encryptCipher.update(data);

		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return null;
	}

	private static byte[] encryptFinal(byte[] data, Cipher encryptCipher, int len) {
		try {
			// Encrypt Final
			return encryptCipher.doFinal(data,0,len);

		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return null;
	}

	public static byte[] decrypt(byte[] data, SecretKey aesKey, Cipher decryptCipher) {
		try {

			// Decrypt			
			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
			CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, decryptCipher);

			cipherOutputStream.write(data);
			cipherOutputStream.flush();
			cipherOutputStream.close();
			return outputStream.toByteArray();

		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return null;
	}

	private static byte[] decryptUpdate(byte[] data, Cipher decryptCipher) {
		try {
			// Decrypt Update
			return decryptCipher.update(data);

		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return null;
	}

	private static byte[] decryptFinal(byte[] data, Cipher decryptCipher, int len) {
		try {
			// Decrypt Final
			return decryptCipher.doFinal(data,0,len);

		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return null;
	}

	public static void encryptInputFile(String inputPath, String outputPath, Cipher encryptCipher){
		try {
			FileInputStream input = new FileInputStream(new File(inputPath));
			FileOutputStream output = new FileOutputStream(new File(outputPath));

			int next, len = 1024;
			byte[] encryptedData; 
			byte[] data = new byte[len];

			while((next = input.read(data,0,len)) != -1){
				if(next == len){
					encryptedData = AES256.encryptUpdate(data,encryptCipher);
					output.write(encryptedData,0,encryptedData.length);
				}
				else{
					encryptedData = AES256.encryptFinal(data,encryptCipher,next);
					output.write(encryptedData,0,encryptedData.length);
				}
				output.flush();
			}
			input.close();
			output.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public static void encryptInputFile(String inputPath, String outputPath, String keystorePath, char[] keystorePassword, char[] keyPassword, String aliasAESKey, byte[] iv){
		try {
			SecretKey aesKey = AES256.loadKeyFromKeyStore(keystorePath, keystorePassword, keyPassword, aliasAESKey);
			
			IvParameterSpec ivspec = new IvParameterSpec(iv);
			Cipher encryptCipher = AES256.initCipherEncrypt(aesKey, ivspec);
			
			FileInputStream input = new FileInputStream(new File(inputPath));
			FileOutputStream output = new FileOutputStream(new File(outputPath));

			int next, len = 1024;
			byte[] encryptedData; 
			byte[] data = new byte[len];

			while((next = input.read(data,0,len)) != -1){
				if(next == len){
					encryptedData = AES256.encryptUpdate(data,encryptCipher);
					output.write(encryptedData,0,encryptedData.length);
				}
				else{
					encryptedData = AES256.encryptFinal(data,encryptCipher,next);
					output.write(encryptedData,0,encryptedData.length);
				}
				output.flush();
			}
			input.close();
			output.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static void decryptInputFile(String inputPath, String outputPath, Cipher decryptCipher){
		try {
			FileInputStream input = new FileInputStream(new File(inputPath));
			FileOutputStream output = new FileOutputStream(new File(outputPath));

			int next, len = 1024;
			byte[] decryptedData; 
			byte[] data = new byte[len];

			while((next = input.read(data,0,len)) != -1){
				if(next == len){
					decryptedData = AES256.decryptUpdate(data,decryptCipher);
					output.write(decryptedData,0,decryptedData.length);
				}
				else{
					decryptedData = AES256.decryptFinal(data,decryptCipher,next);
					output.write(decryptedData,0,decryptedData.length);
				}
				output.flush();
			}
			input.close();
			output.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public static void decryptInputFile(String inputPath, String outputPath, String keystorePath, char[] keystorePassword, char[] keyPassword, String aliasAESKey, byte[] iv){
		try {
			SecretKey aesKey = AES256.loadKeyFromKeyStore(keystorePath, keystorePassword, keyPassword, aliasAESKey);
			
			IvParameterSpec ivspec = new IvParameterSpec(iv);
			Cipher decryptCipher = AES256.initCipherDecrypt(aesKey, ivspec);
			
			FileInputStream input = new FileInputStream(new File(inputPath));
			FileOutputStream output = new FileOutputStream(new File(outputPath));

			int next, len = 1024;
			byte[] decryptedData; 
			byte[] data = new byte[len];

			while((next = input.read(data,0,len)) != -1){
				if(next == len){
					decryptedData = AES256.decryptUpdate(data,decryptCipher);
					output.write(decryptedData,0,decryptedData.length);
				}
				else{
					decryptedData = AES256.decryptFinal(data,decryptCipher,next);
					output.write(decryptedData,0,decryptedData.length);
				}
				output.flush();
			}
			input.close();
			output.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

